Skip to content

fix: /cd command now reloads agent context including AGENTS.md#240

Open
wkramme wants to merge 8 commits intompfaffenberger:mainfrom
wkramme:fix/cd-agents-md-context-reload
Open

fix: /cd command now reloads agent context including AGENTS.md#240
wkramme wants to merge 8 commits intompfaffenberger:mainfrom
wkramme:fix/cd-agents-md-context-reload

Conversation

@wkramme
Copy link
Copy Markdown
Contributor

@wkramme wkramme commented Mar 16, 2026

Problem

The /cd command changes the working directory but the agent context was not being refreshed, causing:

  • AGENTS.md files in new directory to be ignored
  • Stale working directory in system prompts
  • Plugin callbacks not re-firing with new context

Root Causes

  1. Cached _puppy_rules - AGENTS.md content is cached in base_agent.py and never re-read
  2. No agent reload on /cd - Directory changes but agent context stays stale
  3. Plugin callbacks don't re-fire - Any prompt additions based on cwd become stale

Changes

  • Modified /cd command to clear _puppy_rules cache and reload agent
  • Added graceful error handling (directory change succeeds even if reload fails)
  • Added two comprehensive tests to verify reload behavior

Impact

When users /cd to a new directory, the agent now:

  • ✅ Re-reads AGENTS.md from new directory
  • ✅ Updates working directory in system prompt
  • ✅ Re-fires all plugin callbacks (on_load_prompt)
  • ✅ Refreshes all context-dependent prompt components

Testing

All 7 /cd command tests pass, including 2 new tests:

  • test_cd_reloads_agent_context
  • test_cd_handles_agent_reload_failure_gracefully

Summary by CodeRabbit

  • Bug Fixes

    • /cd now triggers an automatic agent context reload after changing directories.
    • Directory changes remain successful even if agent reload fails; a non-fatal error is emitted on failure and an informational message is shown on successful reload. Emission behavior changed from a prior warning to a non-fatal error on reload failure.
  • Tests

    • Added tests verifying reload on /cd and graceful handling when reload raises an exception; updated expectations to match the new error emission behavior.

Fixes issue where /cd command would change the working directory but
the agent context would not be refreshed, causing:
- AGENTS.md files in new directory to be ignored
- Stale working directory in system prompts
- Plugin callbacks not re-firing with new context

Changes:
- Modified /cd command to clear _puppy_rules cache and reload agent
- Added agent reload with graceful error handling
- Added two comprehensive tests to verify reload behavior

This ensures that when users change directories, the agent picks up
project-specific AGENTS.md rules and refreshes all context-dependent
prompt components.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

Warning

Rate limit exceeded

@wkramme has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 10 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7c5c5cf7-6b34-4b13-88f7-41fc055dbde3

📥 Commits

Reviewing files that changed from the base of the PR and between 8af4873 and 9d21fee.

📒 Files selected for processing (2)
  • tests/command_line/test_core_commands_extended.py
  • tests/test_command_handler.py
📝 Walkthrough

Walkthrough

Modifies the /cd command to, after a successful directory change, attempt a best-effort reload of the current agent via reload_code_generation_agent. Successful reload emits an info message; reload exceptions are caught and emitted as non-fatal errors (emit_error) without aborting the directory change.

Changes

Cohort / File(s) Summary
Agent Context Reload Logic
code_puppy/command_line/core_commands.py
After successful chdir, obtains current agent and calls reload_code_generation_agent(); on success emits an info message. Exceptions during reload are caught and reported with emit_error but do not revert the directory change. Import of emit_warning removed.
Test Coverage for Agent Reload
tests/command_line/test_core_commands_extended.py, tests/test_command_handler.py
Adds tests verifying agent reload success and graceful handling of reload exceptions; updates an existing test to expect emit_error instead of emit_warning. Mocks filesystem and agent manager interactions.
Minor Test Formatting Changes
tests/agents/test_base_agent_full_coverage.py, tests/command_line/test_model_settings_menu_coverage.py
Small formatting/side_effect expression changes that do not alter behavior.
Manifest placeholder
manifest_file
Lines changed noted in diff (metadata/manifest adjustments).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as "CLI (/cd)"
    participant OS as "OS Filesystem"
    participant AgentMgr as "Agent Manager"
    participant Logger as "Emitter"

    CLI->>OS: expanduser/isabs/isdir -> chdir
    alt chdir successful
        CLI->>AgentMgr: get_current_agent()
        alt agent exists
            AgentMgr->>AgentMgr: reload_code_generation_agent()
            AgentMgr-->>CLI: success
            CLI->>Logger: emit_info("Agent context updated for new directory")
        else no agent
            CLI->>Logger: emit_info("No agent to reload")
        end
    else chdir failed
        CLI->>Logger: emit_error("Directory change failed")
    end
    alt reload raises exception
        AgentMgr-->>CLI: exception
        CLI->>Logger: emit_error("Could not reload agent context: <error>")
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • mpfaffenberger

Poem

🐰
I hopped into folders, dusted off a clue,
I nudged the agent: "Refresh, start anew!"
If prompts stretch or tumble, the change still holds true,
A gentle error hums, yet the world moves through.
Small rabbit cheers for directories two-by-two.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: the /cd command now reloads agent context and refreshes AGENTS.md, which directly corresponds to the primary fix in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
code_puppy/command_line/core_commands.py (1)

87-91: Avoid mutating _puppy_rules from the command layer.

At Line 89, this reaches into agent internals and duplicates behavior already done inside reload_code_generation_agent() (which invalidates _puppy_rules itself). Keeping invalidation inside the agent reduces coupling.

♻️ Proposed simplification
                 current_agent = get_current_agent()
-                if current_agent:
-                    # Clear cached puppy rules so AGENTS.md is re-read from new directory
-                    current_agent._puppy_rules = None
-                    # Reload agent to rebuild system prompt with new working directory
-                    current_agent.reload_code_generation_agent()
-                    emit_info("Agent context updated for new directory")
+                # reload_code_generation_agent() already invalidates cached rules
+                # and rebuilds prompt/context from the new cwd.
+                current_agent.reload_code_generation_agent()
+                emit_info("Agent context updated for new directory")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/command_line/core_commands.py` around lines 87 - 91, Remove the
direct mutation of the agent internals: delete the line that sets
current_agent._puppy_rules = None and rely on
current_agent.reload_code_generation_agent() to handle invalidation; keep the
comment about reloading if needed but do not touch _puppy_rules from the command
layer and ensure reload_code_generation_agent() is the single place that clears
or rebuilds puppy rules.
tests/command_line/test_core_commands_extended.py (1)

159-160: This assertion is coupled to a private implementation detail.

At Line 159, asserting _puppy_rules directly makes the test brittle to internal refactors. Prefer validating public behavior (reload_code_generation_agent called and /cd succeeds), which you already assert.

🧪 Proposed test hardening
-                                    # Verify agent cache was cleared
-                                    assert mock_agent._puppy_rules is None
-                                    
                                     # Verify agent was reloaded
                                     mock_agent.reload_code_generation_agent.assert_called_once()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/command_line/test_core_commands_extended.py` around lines 159 - 160,
The test currently asserts the private field mock_agent._puppy_rules which
couples the test to an implementation detail; instead remove that private-field
assertion and verify public behavior: assert that
mock_agent.reload_code_generation_agent was called (or use the public
method/flag it sets) and that the subsequent "/cd" command succeeds as already
checked; update the assertion to use mock_agent.reload_code_generation_agent (or
the public API it triggers) rather than accessing _puppy_rules directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@code_puppy/command_line/core_commands.py`:
- Around line 87-91: Remove the direct mutation of the agent internals: delete
the line that sets current_agent._puppy_rules = None and rely on
current_agent.reload_code_generation_agent() to handle invalidation; keep the
comment about reloading if needed but do not touch _puppy_rules from the command
layer and ensure reload_code_generation_agent() is the single place that clears
or rebuilds puppy rules.

In `@tests/command_line/test_core_commands_extended.py`:
- Around line 159-160: The test currently asserts the private field
mock_agent._puppy_rules which couples the test to an implementation detail;
instead remove that private-field assertion and verify public behavior: assert
that mock_agent.reload_code_generation_agent was called (or use the public
method/flag it sets) and that the subsequent "/cd" command succeeds as already
checked; update the assertion to use mock_agent.reload_code_generation_agent (or
the public API it triggers) rather than accessing _puppy_rules directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ce2d58b0-0313-48eb-8bc7-b3000efff6f8

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4936b and e18a4b7.

📒 Files selected for processing (2)
  • code_puppy/command_line/core_commands.py
  • tests/command_line/test_core_commands_extended.py

- Removed direct mutation of agent._puppy_rules from /cd command
- reload_code_generation_agent() now handles invalidation internally
- Updated test to verify public behavior only (reload called)
- Removed brittle assertion on private _puppy_rules field

This reduces coupling and makes the code more maintainable.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/command_line/test_core_commands_extended.py (1)

137-159: LGTM, but remove redundant import.

The test correctly verifies that /cd triggers agent reload. The mocking strategy is sound—patching get_current_agent at the source module works since the import happens at call-time inside the implementation's try block.

Minor: MagicMock is already imported at line 8; the local import on line 139 is unnecessary.

Suggested fix
     def test_cd_reloads_agent_context(self):
         """Test that /cd reloads agent to pick up new directory context (AGENTS.md, etc)."""
-        from unittest.mock import MagicMock
-        
         mock_agent = MagicMock()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/command_line/test_core_commands_extended.py` around lines 137 - 159,
Remove the redundant local import of MagicMock in the
test_cd_reloads_agent_context test: delete the line that re-imports MagicMock
(the second import) and rely on the existing MagicMock imported at the top of
the file; ensure the test still instantiates mock_agent = MagicMock() and calls
handle_cd_command("/cd /new/dir") and verifies
mock_agent.reload_code_generation_agent.assert_called_once().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/command_line/test_core_commands_extended.py`:
- Around line 137-159: Remove the redundant local import of MagicMock in the
test_cd_reloads_agent_context test: delete the line that re-imports MagicMock
(the second import) and rely on the existing MagicMock imported at the top of
the file; ensure the test still instantiates mock_agent = MagicMock() and calls
handle_cd_command("/cd /new/dir") and verifies
mock_agent.reload_code_generation_agent.assert_called_once().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 677aa899-1aee-4e55-bd5b-e0ef0105468f

📥 Commits

Reviewing files that changed from the base of the PR and between e18a4b7 and 35a6a65.

📒 Files selected for processing (2)
  • code_puppy/command_line/core_commands.py
  • tests/command_line/test_core_commands_extended.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • code_puppy/command_line/core_commands.py

wkramme added 2 commits March 16, 2026 08:30
MagicMock is already imported at the top of the test file.
Removed local re-imports in test_cd_reloads_agent_context and
test_cd_handles_agent_reload_failure_gracefully.
The /cd command now uses emit_error for reload failures (not emit_warning).
Also applied ruff formatting for consistency.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_command_handler.py (1)

93-122: ⚠️ Potential issue | 🟠 Major

Patch cleanup mismatch leaks emit_error mock across tests.

You start emit_error at Line 93 but never stop it in finally. This can create cross-test interference.

Suggested fix
     finally:
         mocks["emit_success"].stop()
-        mocks["emit_warning"].stop()
+        mocks["emit_error"].stop()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_command_handler.py` around lines 93 - 122, The test starts the
emit_error mock (mock_emit_error = mocks["emit_error"].start()) but never stops
it in the finally block, leaking the mock across tests; update the finally block
to call mocks["emit_error"].stop() (matching how mocks["emit_success"] and
mocks["emit_warning"] are stopped) so that mock_emit_error is properly torn down
after this test (affecting the test around handle_command and assertions on
mock_agent.reload_code_generation_agent).
🧹 Nitpick comments (1)
tests/command_line/test_core_commands_extended.py (1)

141-158: Strengthen success-path assertion for the new info message.

This test patches emit_info but never verifies it. Since /cd now reports context refresh, assert that the info emission happened to lock the behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/command_line/test_core_commands_extended.py` around lines 141 - 158,
The test currently patches code_puppy.messaging.emit_info but doesn't assert it
was called; update the test around handle_cd_command to capture the patched
emit_info (e.g., assign the patch to a variable like mock_emit_info) and add an
assertion that mock_emit_info was invoked (preferably assert_called_once() or
assert_called_once_with(...) if you know the exact message) to verify the new
"/cd" success path emits the context-refresh info; reference the patched symbol
emit_info and the function under test handle_cd_command (and keep the existing
assertion that mock_agent.reload_code_generation_agent.assert_called_once()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code_puppy/command_line/core_commands.py`:
- Around line 93-95: The message emitted when reload fails uses emit_error(...)
but begins with the literal "Warning:", causing inconsistent severity; update
the call where emit_error is invoked for the reload failure (the string
"Warning: Could not reload agent context: {e}") to remove the "Warning:" prefix
(e.g., "Could not reload agent context: {e}") so the text matches the emit_error
severity, or alternatively call emit_warning(...) instead—modify the emit_* call
and its message accordingly to make severity consistent.

---

Outside diff comments:
In `@tests/test_command_handler.py`:
- Around line 93-122: The test starts the emit_error mock (mock_emit_error =
mocks["emit_error"].start()) but never stops it in the finally block, leaking
the mock across tests; update the finally block to call
mocks["emit_error"].stop() (matching how mocks["emit_success"] and
mocks["emit_warning"] are stopped) so that mock_emit_error is properly torn down
after this test (affecting the test around handle_command and assertions on
mock_agent.reload_code_generation_agent).

---

Nitpick comments:
In `@tests/command_line/test_core_commands_extended.py`:
- Around line 141-158: The test currently patches code_puppy.messaging.emit_info
but doesn't assert it was called; update the test around handle_cd_command to
capture the patched emit_info (e.g., assign the patch to a variable like
mock_emit_info) and add an assertion that mock_emit_info was invoked (preferably
assert_called_once() or assert_called_once_with(...) if you know the exact
message) to verify the new "/cd" success path emits the context-refresh info;
reference the patched symbol emit_info and the function under test
handle_cd_command (and keep the existing assertion that
mock_agent.reload_code_generation_agent.assert_called_once()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db398ec7-0b87-45c6-957b-84583354e8aa

📥 Commits

Reviewing files that changed from the base of the PR and between 35a6a65 and 2c1e37a.

📒 Files selected for processing (3)
  • code_puppy/command_line/core_commands.py
  • tests/command_line/test_core_commands_extended.py
  • tests/test_command_handler.py

Comment thread code_puppy/command_line/core_commands.py
wkramme added 2 commits March 16, 2026 09:07
- Fix mock cleanup leak (stop emit_error instead of emit_warning)
- Remove 'Warning:' prefix from emit_error message for consistency
- Add assertion for emit_info call in test_cd_reloads_agent_context
- Update test to expect new error message format (no 'Warning:' prefix)
Applied black-compatible formatting to 3 test files for consistency.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/test_command_handler.py (1)

117-120: Tighten the reload-error assertion to avoid dead/overly-broad matching.

code_puppy/command_line/core_commands.py emits "Could not reload agent context: ..." on this path, so the "agent reload failed" branch is effectively dead here. Also, asserting on str(call_args) is looser than checking the emitted message argument directly.

Proposed test tightening
-            error_msg = str(mock_emit_error.call_args)
-            assert (
-                "agent reload failed" in error_msg
-                or "Could not reload agent context" in error_msg
-            )
-            assert "boom" in error_msg
+            error_msg = mock_emit_error.call_args[0][0]
+            assert error_msg.startswith("Could not reload agent context:")
+            assert "boom" in error_msg
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_command_handler.py` around lines 117 - 120, Tighten the test
assertion by removing the redundant "agent reload failed" branch and assert the
actual emitted log message argument directly: inspect the mock call (call_args)
to the logger and assert its first positional argument contains the exact prefix
"Could not reload agent context:" (the message emitted by
code_puppy/command_line/core_commands.py) instead of asserting on str(call_args)
or using the loose error_msg membership check; update the test variable usage
(error_msg/call_args) accordingly so it checks call_args.args[0] (or equivalent)
for the expected message prefix.
tests/command_line/test_core_commands_extended.py (1)

185-188: Strengthen failure-path assertions to lock in diagnostics.

Consider asserting exactly one error emission and that the original exception text is present. That makes the non-fatal error contract more robust.

Proposed assertion hardening
-                                    assert mock_error.called
-                                    error_msg = mock_error.call_args[0][0]
-                                    assert "Could not reload agent context" in error_msg
+                                    mock_error.assert_called_once()
+                                    error_msg = mock_error.call_args[0][0]
+                                    assert error_msg.startswith(
+                                        "Could not reload agent context:"
+                                    )
+                                    assert "Reload failed" in error_msg
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/command_line/test_core_commands_extended.py` around lines 185 - 188,
The test currently checks that mock_error was called and that "Could not reload
agent context" is in the logged message; strengthen this by asserting
mock_error.call_count == 1 to ensure exactly one error emission, and also assert
that the original exception text is present in error_msg (use the same exception
message you inject in the failure scenario, e.g., the stringified exception
variable used when simulating the reload failure) so both the diagnostic and
call-count contract are locked in; reference mock_error and error_msg when
adding these assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/command_line/test_core_commands_extended.py`:
- Around line 185-188: The test currently checks that mock_error was called and
that "Could not reload agent context" is in the logged message; strengthen this
by asserting mock_error.call_count == 1 to ensure exactly one error emission,
and also assert that the original exception text is present in error_msg (use
the same exception message you inject in the failure scenario, e.g., the
stringified exception variable used when simulating the reload failure) so both
the diagnostic and call-count contract are locked in; reference mock_error and
error_msg when adding these assertions.

In `@tests/test_command_handler.py`:
- Around line 117-120: Tighten the test assertion by removing the redundant
"agent reload failed" branch and assert the actual emitted log message argument
directly: inspect the mock call (call_args) to the logger and assert its first
positional argument contains the exact prefix "Could not reload agent context:"
(the message emitted by code_puppy/command_line/core_commands.py) instead of
asserting on str(call_args) or using the loose error_msg membership check;
update the test variable usage (error_msg/call_args) accordingly so it checks
call_args.args[0] (or equivalent) for the expected message prefix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 012a5ed5-83ef-4621-8d48-8d437b685f52

📥 Commits

Reviewing files that changed from the base of the PR and between 2c1e37a and 8af4873.

📒 Files selected for processing (5)
  • code_puppy/command_line/core_commands.py
  • tests/agents/test_base_agent_full_coverage.py
  • tests/command_line/test_core_commands_extended.py
  • tests/command_line/test_model_settings_menu_coverage.py
  • tests/test_command_handler.py
✅ Files skipped from review due to trivial changes (2)
  • tests/agents/test_base_agent_full_coverage.py
  • tests/command_line/test_model_settings_menu_coverage.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • code_puppy/command_line/core_commands.py

Per CodeRabbit review:
- Use assert_called_once() instead of assert called
- Check exact prefix with startswith()
- Assert original exception text is present
- Use call_args[0][0] instead of str(call_args)
- Remove redundant 'agent reload failed' dead code check
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant